#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <dlfcn.h>

#include "Errors.h"
#include "Util.h"

static jclass runtimeExceptionClass = NULL;
static jclass channelExceptionClass = NULL;

static char* exceptionMessage(char* msg, int error) {
    // strerror is returning a constant, so no need to free anything coming from strerror
    // error may be negative because some functions return negative values. we should make sure it is always
    // positive when passing to standard library functions.
    return unix_util_prepend(msg, strerror(error < 0 ? -error : error));
}

void unix_errors_throwOutOfMemoryError(JNIEnv* env) {
    jclass exceptionClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
    (*env)->ThrowNew(env, exceptionClass, "");
}

void unix_errors_throwRuntimeException(JNIEnv* env, char* message) {
    (*env)->ThrowNew(env, runtimeExceptionClass, message);
}

void unix_errors_throwChannelExceptionErrorNo(JNIEnv* env, char* message, int errorNumber) {
    char* allocatedMessage = exceptionMessage(message, errorNumber);
    (*env)->ThrowNew(env, channelExceptionClass, allocatedMessage);
    free(allocatedMessage);
}

jstring Java_my_common_Errors_strError(JNIEnv* env, jclass clazz, jint error) {
    return (*env)->NewStringUTF(env, strerror(error));
}

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) {
        return JNI_ERR;
    }
    char* packagePrefix = NULL;
    Dl_info dlinfo;
    jint status = 0;
    if (!dladdr((void*) exceptionMessage, &dlinfo)) {
        fprintf(stderr, "FATAL: native-epoll JNI call to dladdr failed!\n");
        return JNI_ERR;
    }
    packagePrefix = unix_util_parse_package_prefix(dlinfo.dli_fname, "epoll-http", &status);
    if (status == JNI_ERR) {
        fprintf(stderr, "FATAL: native-epoll JNI encountered unexpected dlinfo.dli_fname: %s\n", dlinfo.dli_fname);
        return JNI_ERR;
    }
    jclass localRuntimeExceptionClass = (*env)->FindClass(env, "java/lang/RuntimeException");
    if (localRuntimeExceptionClass == NULL) {
        // pending exception...
        return JNI_ERR;
    }
    runtimeExceptionClass = (jclass) (*env)->NewGlobalRef(env, localRuntimeExceptionClass);
    if (runtimeExceptionClass == NULL) {
        // out-of-memory!
        unix_errors_throwOutOfMemoryError(env);
        return JNI_ERR;
    }

    char* className = unix_util_prepend(packagePrefix, "my/exception/ChannelException");
    jclass localChannelExceptionClass = (*env)->FindClass(env, className);
    free(className);
    className = NULL;
    if (localChannelExceptionClass == NULL) {
        // pending exception...
        return JNI_ERR;
    }
    channelExceptionClass = (jclass) (*env)->NewGlobalRef(env, localChannelExceptionClass);
    if (channelExceptionClass == NULL) {
        // out-of-memory!
        unix_errors_throwOutOfMemoryError(env);
        return JNI_ERR;
    }

    if (packagePrefix != NULL) {
        free(packagePrefix);
        packagePrefix = NULL;
    }
    return JNI_VERSION_1_8;
}